package com.zhx.security.browser;

import com.zhx.core.authentication.mobile.SmsCodeAuthenticationSecurityConfig;
import com.zhx.core.authorize.AuthorizeConfigManager;
import com.zhx.core.common.constants.SecurityConstants;
import com.zhx.core.common.properties.SecurityProperties;
import com.zhx.core.validate.image.ImageCodeSecurityConfig;
import com.zhx.security.browser.session.ZhxExpiredSessionStrategy;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.social.security.SpringSocialConfigurer;

/**
 * Created by zhanghaixuan on 2017/11/1.
 *
 * browser security 配置
 *
 * @Configuration
 * 配置注解 一般情况下spring的配置都用这个注解 就是旧的xml配置
 *
 * WebSecurityConfigurerAdapter 是 springsecurity的配置
 *
 * 通过重写configure方法来实现
 *
 * 有三种configure方法
 *
 * http://www.tianshouzhi.com/api/tutorials/spring_security_4/266  一个比较好的spring文档
 *
 *
 * 一堆验证的filter（可以通过配置使过滤器生效）
 *
 *
 * ExceptionTranslationFilter
 *
 * FilterSecurityInterceptor （最后的大门）
 *
 * restapi
 */

@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    //注入springsecurity 密码加密逻辑
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //注入自定义的成功处理器
    @Autowired
    private AuthenticationSuccessHandler myAuthentionSuccessHandler;

    //注入自定义的失败处理器
    @Autowired
    private AuthenticationFailureHandler myAuthentionFailedHandler;

    //自定义的配置项目
    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private UserDetailsService myUserDetailService;

    //注入自己实现的短信登录校验过程的配置
    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;

    @Autowired
    private ImageCodeSecurityConfig imageCodeSecurityConfig;

    //注入刚开始连接数据库的dataSource
    @Autowired
    private DataSource dataSource;


    @Autowired
    private SpringSocialConfigurer zhxSocialSpringConfigurer;

    @Autowired
    private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;

    @Autowired
    private InvalidSessionStrategy invalidSessionStrategy;


    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler;

    @Autowired
    private AuthorizeConfigManager authorizeConfigManager;

    /**
     * 配置记住我的功能  需要指定一个数据源来实现
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        //tokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }

    /**
     * 浏览器定义的部分
     * @param http
     * @throws Exception
     */
    public void browserHttpSecurityConfig(HttpSecurity http) throws Exception {
        //使用页面登录
        //http.httpBasic()
        //使用表单登录
        http.formLogin()
                //自定义登陆页面
                .loginPage(SecurityConstants.DEFAULT_UNAUTHENRICATION_URL)
                .loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
                //指定自定义的成功处理器
                .successHandler(myAuthentionSuccessHandler)
                .failureHandler(myAuthentionFailedHandler)
                //remember me功能实现
                .and()
                .rememberMe()
                //token存放的地方
                .tokenRepository(persistentTokenRepository())
                //token保存的时间
                .tokenValiditySeconds( securityProperties.getBrowser().getRememberMeSeconds())
                //如果成功后调取的userdetailsservice
                .userDetailsService(myUserDetailService);
    }


    /**
     * 重构浏览器安全控制设置
     *
     * 剥离 不同的模块配置
     *
     * 通过apply方法注入不同的配置
     * @param http
     * @throws Exception
     */

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        browserHttpSecurityConfig(http);

        http.apply(smsCodeAuthenticationSecurityConfig)
                .and()
             .apply(imageCodeSecurityConfig)
                .and()
             .apply(zhxSocialSpringConfigurer)
                .and()
                //session 控制 当session过期的时候 会导向invalidSessionUrl
                //maximumSessions 一个账户最多的session数量
                //expiredSessionStrategy 可以记录是怎么样实现了session的并发的
             .sessionManagement()
                .invalidSessionStrategy(invalidSessionStrategy)
                .maximumSessions(securityProperties.getBrowser().getSession().getMaximumSessions())
                //maxSessionsPreventsLogin 当这句话为true的时候 当一个用户登录了 另外一个就不能登录
                .maxSessionsPreventsLogin(securityProperties.getBrowser().getSession().isMaxSessionsPreventsLogin())
                .expiredSessionStrategy(sessionInformationExpiredStrategy)
                .and()
                .and()
             .logout()
                //自定义退出的接口路径
                //.logoutUrl("")
                .logoutSuccessHandler(logoutSuccessHandler)
                //退出后删除cookie
                //.deleteCookies()
                .and()
             .csrf()
                .disable();


        authorizeConfigManager.config(http.authorizeRequests());
    }
}
